"""接收界面相关事件的回调"""
import os.path
import traceback

import backtrader as bt
import pandas as pd

from tools.framework import load_module_from_path, get_latest_file, get_ui_value
from core.constant import *
from module.static_module.parent.model import DynamicModule
from conf import conf


class BacktestingModel(DynamicModule):
    def __init__(self, master):
        super().__init__(master, Module.Backtesting)
        # 实体类映射视图类变量数据
        self.data_name_ls = []
        self.strategy_name_ls = []
        self.research_plan_ls = []
        self.result_folder_ls = []
        # 映射视图类变量数据默认值
        self.data_name = None
        self.strategy_name = None
        self.research_plan = None
        self.result_folder = None
        # 实体类结构变量
        self.equity_data_dc = None
        self.order_data_dc = None
        self.trade_data_dc = None
        self.canvas_ls = []  # 记录canvas绘图数据，第一个元素为回测总数量，往后每个元素均为固定长度列表，代表一个可能被绘制的像素点

    def sec_init(self):
        from tools.framework import gen_result_folder_name  # 避免循环调用

        # 实体类映射视图类变量数据
        self.data_name_ls = self.master.file_manager.data_def_group_dc[DataDefGroup.Mk.value] + \
                            self.master.file_manager.data_def_group_dc[DataDefGroup.MkFt.value]
        self.strategy_name_ls = self.master.file_manager.data_def_group_dc[DataDefGroup.BTSName.value]
        self.research_plan_ls = self.master.file_manager.data_def_group_dc[DataDefGroup.BSummary.value]
        self.result_folder_ls = [gen_result_folder_name(ResultFolder.BT)]

    def get_ui_params(self):
        # 获取ui界面的相关参数
        values, indices = self.master.main_window_view.dynamic_module_view.auto_layout.get_value(
            LabelMember.DataName)
        self.data_name = get_ui_value(values, indices, WidgetCategory.Combobox)
        values, indices = self.master.main_window_view.dynamic_module_view.auto_layout.get_value(
            LabelMember.StrategyName)
        self.strategy_name = get_ui_value(values, indices, WidgetCategory.Combobox)
        values, indices = self.master.main_window_view.dynamic_module_view.auto_layout.get_value(
            LabelMember.ResearchPlan)
        self.research_plan = get_ui_value(values, indices, WidgetCategory.Combobox)
        values, indices = self.master.main_window_view.dynamic_module_view.auto_layout.get_value(
            LabelMember.ResultFolder)
        self.result_folder = get_ui_value(values, indices, WidgetCategory.Entry)

    def on_run_thread(self):
        try:
            self.get_ui_params()
            # 获取回测数据
            data_path = self.master.file_manager.describe_all_dc[self.data_name].path
            data_df_dc: dict[str, pd.DataFrame] = self.master.file_manager.read_dc_csv(data_path)
            # 导入策略文件
            strategy_path = os.path.join(self.master.file_manager.strategy_path, self.strategy_name)
            strategy_py = load_module_from_path(strategy_path)
            import sys
            module_name = os.path.basename(strategy_path).split('.')[0]
            sys.modules[module_name] = strategy_py
            strategy = strategy_py.DDQStrategy
            # 从策略文件中获取策略运行参数
            strategy_p = strategy_py.P
            strategy_symbol = strategy_py.Symbol
            # 监测策略中的Symbol类中值是否与数据中的symbol值一致
            data_describe = self.master.file_manager.describe_all_dc[os.path.basename(data_path)]
            for mem in strategy_symbol:
                if mem.value not in data_describe.symbol_ls:
                    raise ValueError(f"数据{os.path.basename(data_path)}中的Symbol（标的）与加载的策略文件不匹配。{mem.value}")
            p_dc = {}
            for mem in strategy_p:
                p_dc[mem.name] = mem.value
            # 补充参数
            p_dc["result_folder_path"] = os.path.join(self.master.file_manager.result_path, ResultFolder.BT.value, self.result_folder)
            p_dc["am_factor_c"] = load_module_from_path(self.master.file_manager.factor_am_path).ArrayManagerFactor
            p_dc["super_model"] = self
            # 启动bt引擎
            self.backtrader(data_df_dc, strategy, p_dc)
        except Exception as e:
            info = f"[{self.module.value}]任务运行出错，错误信息{e}"
            traceback.print_exc()
            self.master.file_manager.log_engine.emit(info, LogName.Operate)
            pass
        # 任务结束
        self.task_end()

    def backtrader(self, data_df_dc, strategy, p_dc):
        cash = p_dc["cash"]
        shortcash = p_dc["shortcash"]
        commission = p_dc["commission"]

        result_folder_path = p_dc["result_folder_path"]

        # 自定义PandasData类
        # class CusPandasData(bt.feeds.PandasData):
        #     lines = ('c_group', )
        #     params = (('c_group', 'c_group'), )
        #
        # 编写backtrader回测函数
        cerebro = bt.Cerebro(stdstats=True)  # create a "Cerebro" engine instance
        # Create a support feed
        for symbol, data_df in data_df_dc.items():
            # 创建pandasData自定义类
            factor_ls = list(x for x in data_df.columns.tolist() if x not in conf.Framework.not_factor_ls.value)
            if factor_ls:
                lines = tuple(factor_ls)
                params_generator = ((x, x) for x in factor_ls)
                params = tuple(params_generator)
                CusPandasData = type('CusPandasData', (bt.feeds.PandasData,), {'lines': lines, 'params': params})
                data = CusPandasData(dataname=data_df)
            else:
                data = bt.feeds.PandasData(dataname=data_df)
            cerebro.adddata(data, name=symbol)  # Add the support feed
        cerebro.broker.set_cash(cash)
        cerebro.broker.set_shortcash(shortcash)
        cerebro.broker.setcommission(commission)
        # cerebro.addobserver(bt.observers.Broker)
        # cerebro.addobserver(bt.observers.BuySell)
        cerebro.addobserver(bt.observers.DrawDown)
        cerebro.addobserver(bt.observers.FundValue)
        # cerebro.addindicator(CacheInd)
        cerebro.addstrategy(strategy, p_dc=p_dc)

        cerebro.run()  # run it all
        try:
            gen_file = eval(p_dc["gen_file"])
        except:
            gen_file = False
        if gen_file:
            fig_path = os.path.join(result_folder_path, "BTFigure.jpg")
            # 重写Plot的show方法使其不显示，只保存
            from backtrader.plot.plot import Plot

            class NoShowPlot(Plot):
                def show(self):
                    self.mpyplot.savefig(fig_path)

            cerebro.plot(plotter=NoShowPlot(), width=64, height=36)
            # 打开保存的回测结果图像并展示
            # self.master.file_manager.show_img(fig_path)

    def on_only_report(self):
        from module.logic_module.researcher.backtest_summary import BacktestSummary

        self.get_ui_params()
        result_folder_path = os.path.join(self.master.file_manager.result_path, ResultFolder.BT.value, self.result_folder)
        # 导入回测生成的数据
        self.equity_data_dc = self.master.file_manager.read_dc_csv(os.path.join(result_folder_path, f"{DataCategory.Equity.value}.csv"))
        self.order_data_dc = self.master.file_manager.read_dc_csv(os.path.join(result_folder_path, f"{DataCategory.Order.value}.csv"))
        self.trade_data_dc = self.master.file_manager.read_dc_csv(os.path.join(result_folder_path, f"{DataCategory.Trade.value}.csv"))
        # 导入回测分析方法
        # 使用plan分析数据并在结果文件夹生成md文件
        plan_ins = BacktestSummary(self)
        plan_method = getattr(plan_ins, self.research_plan, None)
        if plan_method:
            plan_method()  # 具体方法结合界面确定
        else:
            info = f"[{self.module.value}]报告方案调用错误。"
            self.master.file_manager.log_engine.emit(info, LogName.Operate)
            return
        # result_dir_path = os.path.join(self.master.file_manager.result_path, ResultFolder.BT.value, self.result_folder)
        # # 定义 Markdown 文件的路径
        # md_file_path = get_latest_file(result_dir_path, ".md")
        # # 转为html并打开
        # self.master.file_manager.md2html(md_file_path, True)

    def save_action_data(self):
        # 将本次回测的交易数据放至文件夹

        pass

    def save_mkftsg_data(self):
        # 将初始数据加上signal保存至market_factor_signal文件夹下

        pass
